/*
 * Lenovo Group
 * Copyright (c) 2015-2016 All Rights Reserved.
 * 
 * Project Name:assign-tracer
 * Create Time: 2016年2月23日 上午12:13:50
 */
package com.lenovo.lmrp.server.assigntracer.repository;

import java.util.Date;
import java.util.List;
import java.util.Objects;

import javax.inject.Inject;

import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngines;
import org.activiti.engine.repository.ProcessDefinition;

import com.lenovo.lmrp.api.model.entity.ProcessActivityPO;
import com.lenovo.lmrp.api.model.entity.ProcessPO;
import com.lenovo.lmrp.api.model.entity.ProcessPO.ProcessStatus;
import com.lenovo.lmrp.api.model.entity.TicketProcessPO;
import com.lenovo.lmrp.api.model.entity.TicketProcessPO.ProcessInstStatus;
import com.lenovo.lmrp.server.assigntracer.domain.workflow.IProcessService;
import com.lenovo.lmrp.server.assigntracer.domain.workflow.IWorkflowRepository;
import com.lenovo.lmrp.server.assigntracer.domain.workflow.ProcessBO;
import com.lenovo.lmrp.server.assigntracer.domain.workflow.TicketProcessBO;
import com.lenovo.sdc.framework.dao.ADaoFactory;
import com.lenovo.sdc.framework.dao.ICommonDAO;
import com.lenovo.sdc.framework.dao.RecordFilter;
import com.lenovo.sdc.framework.util.CommonUtil;
import com.lenovo.sdc.framework.util.Condition;
import com.lenovo.sdc.framework.util.Condition.OperatorEnum;
import com.lenovo.sdc.framework.util.Condition.SortEnum;
import com.lenovo.sdc.framework.util.exception.ErrorCode;
import com.lenovo.sdc.framework.util.exception.TraceableException;

/**
 * load PO/BO from cache or DB
 * 
 * @author wujin
 * @version v1.0
 */
public class WorkflowRepository implements IWorkflowRepository {

    private IProcessService processService;
    
    @Inject
    public void set(IProcessService processService) {
        this.processService = processService;
    }
    
    @Override
    public ProcessBO createProcessBO(String processID,boolean withActivity) {
        try (ICommonDAO dao = ADaoFactory.getDefault().createDAO()) {
            ProcessPO processPO = dao.get(ProcessPO.class, processID);
            if (Objects.isNull(processPO))
                throw new TraceableException(ErrorCode.DB_RECORD_NOTFOUND, " processid=" + processID);
            
            if (withActivity) {
                RecordFilter filter = new RecordFilter();
                filter.addCondition(new Condition<String, Object>("processid", processID));
                processPO.setActivitys(dao.filteByCondition(ProcessActivityPO.class, filter));
            }
            
            return new ProcessBO(processService, processPO);
        }
        catch (TraceableException exp) {
            throw exp;
        }
        catch (Exception exp) {
            throw new TraceableException(ErrorCode.DB_QUERY_FAILED, exp);
        }
    }

    public TicketProcessBO createTicketProcessBO(String itsmCode, String ticketID, boolean withActivity) {
        TicketProcessPO tpp = getRuningTicketProcess(itsmCode, ticketID);
        if (withActivity)
            tpp.setActivites(this.getProcessActivities(tpp.getProcessId()));
        return new TicketProcessBO(processService, tpp);
    }
    
    @Override
    public void storeWorkflow(String itsmcode, ProcessPO processPO) {
        if (CommonUtil.isNullOrEmpty(itsmcode) || Objects.isNull(processPO))
            throw new TraceableException(ErrorCode.COMM_PARAM_NULL, " itsmcode and processPO is null");

        if (Objects.isNull(processPO.getActivitys()) || processPO.getActivitys().isEmpty())
            throw new TraceableException(ErrorCode.COMM_PARAM_NULL, "process activity is null");

        boolean isNew = true;
        try (ICommonDAO dao = ADaoFactory.getDefault().createDAO()) {
            dao.beginTrans();
            
            ProcessPO oldPO = dao.get(ProcessPO.class, processPO.getProcessId());
            if (Objects.isNull(oldPO)) {
                isNew = true;
                dao.save(processPO);
                for (ProcessActivityPO activity : processPO.getActivitys()) {
                    dao.save(activity);
                }
                // set other process with same processKey to deleted status 
                dao.executeHQL("update ProcessPO set status=? where processid != ? and processkey = ? and itsmcode = ?", 
                    new Object[]{ProcessStatus.DELETING, processPO.getProcessId(), processPO.getProcessKey(), itsmcode});
            }
            else {
                // update process recored
                isNew = false;
                dao.update(processPO);
                // update activity_form record, we don't support add or delete process node in one process
                for (ProcessActivityPO activity : processPO.getActivitys()) {
                    dao.update(activity);
                }
            }

            dao.endTrans();
        }
        catch (Exception exp) {
            //delete process from activiti DB if that is new process
            if (isNew) {
                ProcessEngine processEngine = ProcessEngines.getDefaultProcessEngine();
                ProcessDefinition pd = processEngine.getRepositoryService().createProcessDefinitionQuery()
                    .processDefinitionId(processPO.getProcessId()).singleResult();
                if (Objects.nonNull(pd))
                    processEngine.getRepositoryService().deleteDeployment(pd.getDeploymentId());
            }
            
            throw new TraceableException(ErrorCode.DB_BATCH_FAILED, exp);
        }
    }

    @Override
    public TicketProcessPO createTicketProcessPO(String processID, String itsmCode, String ticketID) {
        try (ICommonDAO dao = ADaoFactory.getDefault().createDAO()) {
            RecordFilter filter = new RecordFilter();
            filter.addCondition(new Condition<String, Object>("ticketid", ticketID))
                .addCondition(new Condition<String, Object>("itsmcode", itsmCode))
                .addCondition(new Condition<String, Object>("status", ProcessInstStatus.STOPED, OperatorEnum.NE));
            List<TicketProcessPO> ticketProcessList = dao.filteByCondition(TicketProcessPO.class, filter);
            if (Objects.isNull(ticketProcessList) || ticketProcessList.isEmpty()) {
                TicketProcessPO tp = new TicketProcessPO();
                tp.setTicketId(ticketID);
                tp.setProcessId(processID);
                tp.setItsmCode(itsmCode);
                tp.setStartTime(new Date());
                tp.setTStatus(ProcessInstStatus.STARTED);
                tp.setInstanceId(processService.createProcessInstance(processID));
                
                dao.beginTrans();
                dao.save(tp);
                dao.endTrans();
                
                return tp;
            }
            else {
                throw new TraceableException(ErrorCode.COMM_PARAM_INVALID, " ticket " + ticketID + " has a running process");
            }
        }
        catch (TraceableException exp) {
            throw exp;
        }
        catch (Exception exp) {
            throw new TraceableException(ErrorCode.DB_QUERY_FAILED, exp);
        }
    }

    public TicketProcessPO getRuningTicketProcess(String itsmCode, String ticketID) {
        try (ICommonDAO dao = ADaoFactory.getDefault().createDAO()) {
            RecordFilter filter = new RecordFilter();
            filter.addCondition(new Condition<String, Object>("ticketid", ticketID))
                .addCondition(new Condition<String, Object>("itsmcode", itsmCode))
                .addCondition(new Condition<String, Object>("status", ProcessInstStatus.STOPED, OperatorEnum.NE));
            List<TicketProcessPO> ticketProcessList = dao.filteByCondition(TicketProcessPO.class, filter);
            if (Objects.isNull(ticketProcessList) || ticketProcessList.isEmpty()) {
                throw new TraceableException(ErrorCode.DB_RECORD_NOTFOUND, " ticket " + ticketID + " hasn't process instance");
            }
            else {
                TicketProcessPO po = ticketProcessList.remove(0);
                ticketProcessList.clear();
                
                return po;
            }
        }
        catch (Exception exp) {
            throw new TraceableException(ErrorCode.DB_QUERY_FAILED, exp);
        }
    }
    
    @Override
    public TicketProcessPO saveTicketProcessPO(TicketProcessPO ticketProcessPO) {
        try (ICommonDAO dao = ADaoFactory.getDefault().createDAO()) {
            if (Objects.nonNull(ticketProcessPO)) {
                dao.beginTrans();
                dao.save(ticketProcessPO);
                dao.endTrans();
                return ticketProcessPO;
            }
            else {
                throw new TraceableException(ErrorCode.COMM_PARAM_INVALID, " ticketProcessPO is NULL");
            }
        }
        catch (TraceableException exp) {
            throw exp;
        }
        catch (Exception exp) {
            throw new TraceableException(ErrorCode.DB_QUERY_FAILED, exp);
        }
    }

    @Override
    public List<ProcessActivityPO> getProcessActivities(String processID) {
        try (ICommonDAO dao = ADaoFactory.getDefault().createDAO()) {
            RecordFilter filter = new RecordFilter();
            filter.addCondition(new Condition<String, Object>("processid", processID))
            .addCondition(new Condition<String, Object>("activityId", null, OperatorEnum.NONE, SortEnum.ASC));
            List<ProcessActivityPO> activityList = dao.filteByCondition(ProcessActivityPO.class, filter);
            if (Objects.isNull(activityList) || activityList.isEmpty()) {
                throw new TraceableException(ErrorCode.DB_RECORD_NOTFOUND, " process " + processID + " hasn't activity forms");
            }
            
            return activityList;
        }
        catch (Exception exp) {
            throw new TraceableException(ErrorCode.DB_QUERY_FAILED, exp);
        }
    }
}
